<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */
declare (strict_types=1);

namespace lflow\ckpt;

use lflow\core\Execution;
use lflow\exceptions\WorkFlowException;
use lflow\handlers\IHandler;
use lflow\lib\util\AssertHelper;
use lflow\lib\util\ClassUtil;
use lflow\lib\util\ObjectHelper;
use lflow\lib\util\StringHelper;
use ReflectionClass;

/**
 * 自定义元素
 *
 * @author Mr.April
 * @since  1.0
 */
class CustomCkpt extends WorkFlowCkpt
{
    /**
     * 需要执行的class类路径
     */
    private string $clazz;

    /**
     * 需要执行的class对象的方法名称
     */
    private string $methodName;

    /**
     * 执行方法时传递的参数表达式变量名称
     */
    private string $args;

    /**
     * 执行的返回值变量
     */
    private string $var;

    /**
     * 加载模型时初始化的对象实例
     */
    private object $invokeObject;

    /**
     *
     * @param \lflow\core\Execution $execution
     *
     * @throws \ReflectionException
     */
    public function exec(Execution $execution): void
    {
        if (!isset($this->invokeObject)) {
            $this->invokeObject = ClassUtil::instantiateClass($this->clazz);
        }

        $this->invokeObject = ClassUtil::instantiateClass($this->clazz);

        if ($this->invokeObject == null) {
            throw new WorkFlowException("自定义模型[class=" . $this->clazz . "]实例化对象失败");
        }

        if ($this->invokeObject instanceof IHandler) {
            $handler = $this->invokeObject;
            $handler->handle($execution);
        } else {
            $reflect = new ReflectionClass($this->invokeObject);
            $method  = $reflect->getMethod($this->methodName);
            if (!$method || !$method->isPublic() || $method->isAbstract()) {
                throw new WorkFlowException("自定义模型[class=" . $this->clazz . "]无法找到方法名称:" . $this->methodName);
            }

            $arguments   = $this->getArgsMap($execution->getArgs(), $this->args);
            $returnValue = $method->invoke($this->invokeObject, ...$arguments);

            //赋值输出变量
            $exploded = explode(",", $this->var);
            $filtered = array_filter($exploded, function ($value) {
                return $value !== "";
            });
            $newArgs  = $execution->getArgs();
            foreach ($filtered as $value) {
                $returnValue       = is_array($returnValue) ? (object)$returnValue : $returnValue;
                $currentValue      = ObjectHelper::getObjectValue($returnValue, $value);
                $newArgs->{$value} = $currentValue;
            }
        }

        $execution->getEngine()->task()->history($execution, $this);
        $this->runOutTransition($execution);
    }

    public function getClazz(): string
    {
        return $this->clazz ?? '';
    }

    public function setClazz(string|null $clazz): void
    {
        $classZ      = str_replace('/', '\\', !empty($clazz) ? $clazz : '');
        $this->clazz = $classZ;
        if (StringHelper::isNotEmpty($classZ)) {
            $invokeObject = ClassUtil::instantiateClass($classZ);
            AssertHelper::notNull($invokeObject, $classZ . 'class 自定义节点类不存在');
            if (!empty($invokeObject)) {
                $this->invokeObject = $invokeObject;
            }
        }
    }

    public function getMethodName(): string
    {
        return $this->methodName;
    }

    public function setMethodName(string|null $methodName): void
    {
        $this->methodName = $methodName;
    }

    public function getArgs(): string|null
    {
        return $this->args;
    }

    private function getArgsMap($execArgs, $args): ?array
    {
        $result = [];
        if (strlen($args) > 0) {
            $argArray = explode(',', $args);
            foreach ($argArray as $value) {
                $result[] = ObjectHelper::getObjectValue($execArgs, $value);
            }
        }
        return $result;
    }

    public function setArgs(string|null $args): void
    {
        $this->args = $args;
    }

    public function getVar(): string|null
    {
        return $this->var;
    }

    public function setVar(string|null $var): void
    {
        $this->var = !empty($var) ? $var : '';
    }

}
